package irisext

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
	"time"

	"gitee.com/terender/sfw/signature"

	"github.com/kataras/iris/v12"
)

// SignatureHandler ...
type SignatureHandler struct {
	key string
}

// NewSignaturehandler ...
func NewSignaturehandler(key string) *SignatureHandler {
	return &SignatureHandler{
		key: key,
	}
}

func cachedBody(ctx iris.Context) []byte {
	cache := ctx.Values().Get(`body`)
	if cache != nil {
		body, _ := cache.([]byte)
		return body
	}
	body, _ := ioutil.ReadAll(ctx.Request().Body)
	ctx.Request().Body.Close()
	ctx.Request().Body = ioutil.NopCloser(bytes.NewBuffer(body))
	ctx.Values().Set(`body`, body)
	return body
}

func (h *SignatureHandler) ServeHTTP(ctx iris.Context) {

	sign := ctx.GetHeader(`signature`)
	body := cachedBody(ctx)
	md5hash := md5.Sum(append(body, []byte(h.key)...))
	mysign := hex.EncodeToString(md5hash[:])

	if mysign != sign {
		ctx.Values().Set(`Authenticated`, false)
	} else {
		ctx.Values().Set(`Authenticated`, true)
	}
	ctx.Header(`Authenticated`, `Signature not valid, you have to sign the request body with the correct key`)
	ctx.Next()
}

// CheckAuthorization 检查请求中的授权数据
// 服务器通用的相互调用授权机制，在授权中指定授权者名称，时间戳和签名
// 如果被调用的服务有配置授权者的公钥，并且验签通过，则允许此次调用
// 签名方法为把时间戳拼接至请求正文最后，然后签名
// 如果时间戳超过服务器当前事件24小时以上，则调用失败
func CheckAuthorization(ctx iris.Context) error {
	auth := ctx.GetHeader(`Z-Authorization`)
	vec := strings.Split(auth, `;`)
	if len(vec) != 3 {
		return fmt.Errorf(`Z-Authorization is not valid`)
	}

	/// 授权者
	author := strings.TrimSpace(vec[0])
	if author == `` {
		return fmt.Errorf(`Z-Authorization is not valid: no author`)
	}

	/// 时间戳
	timestamp := strings.TrimSpace(vec[1])
	if timestamp == `` {
		return fmt.Errorf(`Z-Authorization is not valid: timestamp null`)
	}
	ts, err := strconv.ParseInt(timestamp, 10, 64)
	if err != nil {
		return fmt.Errorf(`Z-Authorization is not valid: timestamp invalid`)
	}
	t := time.Unix(ts, 0)
	d := time.Now().Sub(t)
	if d < 0 {
		d = -d
	}
	if d > time.Hour*24 {
		return fmt.Errorf(`Z-Authorization is not valid: timestamp expired`)
	}

	/// 签名
	sign, err := base64.StdEncoding.DecodeString(strings.TrimSpace(vec[2]))
	if err != nil {
		return fmt.Errorf(`Z-Authorization is not valid: signature parse failed ` + err.Error())
	}

	/// 验签
	pubkey, err := signature.GetKey(author).Pub()
	if err != nil {
		return fmt.Errorf(`Read public key failed: ` + err.Error())
	}

	body, err := ioutil.ReadAll(ctx.Request().Body)
	if err != nil {
		return err
	}
	ctx.Request().Body.Close()
	data := append(body, []byte(timestamp)...)
	_, err = signature.Verify(data, sign, pubkey)
	if err != nil {
		return fmt.Errorf(`Verify signature failed: ` + err.Error())
	}
	ctx.Request().Body = ioutil.NopCloser(bytes.NewBuffer(body))
	return nil
}

// Authorization 向http请求中添加授权数据
func Authorization(req *http.Request, author string, body []byte) error {
	privKey, err := signature.GetKey(author).Priv()
	if err != nil {
		return err
	}
	timestamp := fmt.Sprint(time.Now().Unix())
	data := append(body, []byte(timestamp)...)
	signed, err := signature.Sign(data, privKey)
	if err != nil {
		return err
	}
	auth := fmt.Sprintf(`%s; %s; %s`, author, timestamp, base64.StdEncoding.EncodeToString(signed))
	req.Header.Add(`Z-Authorization`, auth)
	return nil
}
